// (C) 2022 Intel Corporation. All rights reserved.
// Your use of Intel Corporation's design tools, logic functions and other 
// software and tools, and its AMPP partner logic functions, and any output 
// files from any of the foregoing (including device programming or simulation 
// files), and any associated documentation or information are expressly subject 
// to the terms and conditions of the Intel Program License Subscription 
// Agreement, Intel FPGA IP License Agreement, or other applicable 
// license agreement, including, without limitation, that your use is for the 
// sole purpose of programming logic devices manufactured by Intel and sold by 
// Intel or its authorized distributors.  Please refer to the applicable 
// agreement for further details.

`timescale 100 ps / 1 ps

module i3c_phy_link_mgmt import i3c_pkg::*; (
    // clock interface
    input  wire                 clk,
    input  wire                 reset_n,

    // Link signals
    input  wire                 scl_data_in,
    output reg                  scl_data_out,
    output reg                  scl_oe,

    input  wire                 sda_data_in,
    output reg                  sda_data_out,
    output reg                  sda_oe,

    // Configuration signals
    input  operation_mode_t     operation_mode, 
    input  device_t             device,
    input  [ 2:0]               mode,
    input  link_state_t         link_state,
    input  link_mode_t          link_mode,
    input  wire                 gen_data,

    // Mgmt signals
    input  request_t            request,
    input  wire                 busy,

    output wire                 cap_data,
    output reg                  cap_data_valid,

    input  transition_t         transition_request,
    output transition_status_t  transition_status,
    output reg                  transition_status_valid,

    output reg                  ack_nack_status,
    output reg                  ack_nack_status_valid,

    output reg                  stage_last,

    output wire                 ibi_detected,

    output reg                  pur_oe
);

    typedef enum logic [8:0] {
        SCL_LOW_PERIOD_I3C_12_5MHZ     = 9'd4,
        SCL_LOW_PERIOD_I3C_8MHZ        = 9'd8,
        SCL_LOW_PERIOD_I3C_6MHZ        = 9'd12,
        SCL_LOW_PERIOD_I3C_4MHZ        = 9'd22,
        SCL_LOW_PERIOD_I3C_2MHZ        = 9'd46,
        SCL_LOW_PERIOD_I3C_OPEN_DRAIN  = 9'd246,
        SCL_LOW_PERIOD_I2C_1MHZ        = 9'd50,
        SCL_LOW_PERIOD_I2C_400KHZ      = 9'd125,
        SCL_LOW_PERIOD_I2C_100KHZ      = 9'd500
    } scl_low_period_t;

    typedef enum logic [8:0] {
        SCL_HIGH_PERIOD_I3C_SHORT       = 9'd3,
        SCL_HIGH_PERIOD_I3C_SHORT_DIV2  = 9'd2,
        SCL_HIGH_PERIOD_I3C_LONG        = 9'd7,
        SCL_HIGH_PERIOD_I3C_LONG_DIV2   = 9'd4,
        SCL_HIGH_PERIOD_I2C_1MHZ        = 9'd49,
        SCL_HIGH_PERIOD_I2C_1MHZ_DIV2   = 9'd25,
        SCL_HIGH_PERIOD_I2C_400KHZ      = 9'd124,
        SCL_HIGH_PERIOD_I2C_400KHZ_DIV2 = 9'd62,
        SCL_HIGH_PERIOD_I2C_100KHZ      = 9'd499,
        SCL_HIGH_PERIOD_I2C_100KHZ_DIV2 = 9'd250
    } scl_high_period_t;

    transition_t  transition_captured;

    // SCL low period counter
    logic             scl_status;
    logic             scl_status_ff;
    logic             scl_status_r_edge;
    logic             scl_status_middle;
    scl_low_period_t  scl_low_period;
    logic [ 8:0]      scl_low_period_cntr;
    scl_high_period_t scl_high_period;
    scl_high_period_t scl_high_period_div2;
    logic [ 8:0]      scl_high_period_cntr;

    assign cap_data       = sda_data_in;

    // Select SCL low period
    always_ff @(posedge clk or negedge reset_n) begin
        if (~reset_n) begin
            scl_low_period <= SCL_LOW_PERIOD_I2C_400KHZ;
        end
        else begin
            if (device == DEVICE_I3C) begin
                if (link_mode == PUSH_PULL) begin
                    if (mode == 3'h0) begin
                        scl_low_period <= SCL_LOW_PERIOD_I3C_12_5MHZ;
                    end
                    else if (mode == 3'h1) begin
                        scl_low_period <= SCL_LOW_PERIOD_I3C_8MHZ;
                    end
                    else if (mode == 3'h2) begin
                        scl_low_period <= SCL_LOW_PERIOD_I3C_6MHZ;
                    end
                    else if (mode == 3'h3) begin
                        scl_low_period <= SCL_LOW_PERIOD_I3C_4MHZ;
                    end
                    else if (mode == 3'h4) begin
                        scl_low_period <= SCL_LOW_PERIOD_I3C_2MHZ;
                    end
                    else begin
                        // Error
                    end
                end
                else begin // link_mode == OPEN_DRAIN
                    scl_low_period <= SCL_LOW_PERIOD_I3C_OPEN_DRAIN;
                end
            end
            else begin // device == DEVICE_I2C
                if (mode == 3'h0) begin
                    scl_low_period <= SCL_LOW_PERIOD_I2C_1MHZ;
                end
                else if (mode == 3'h1) begin
                    scl_low_period <= SCL_LOW_PERIOD_I2C_400KHZ;
                end
                else if (mode == 3'h2) begin
                    scl_low_period <= SCL_LOW_PERIOD_I2C_100KHZ;
                end
                else if (mode == 3'h3) begin
                    scl_low_period <= SCL_LOW_PERIOD_I2C_100KHZ;
                end
                else if (mode == 3'h4) begin
                    scl_low_period <= SCL_LOW_PERIOD_I2C_100KHZ;
                end
                else begin
                    // Error
                end
            end
        end
    end

    // Select SCL high period
    always_ff @(posedge clk or negedge reset_n) begin
        if (~reset_n) begin
            scl_high_period <= SCL_HIGH_PERIOD_I2C_100KHZ;
            scl_high_period_div2 <= SCL_HIGH_PERIOD_I2C_100KHZ_DIV2;
        end
        else begin
            if (device == DEVICE_I3C) begin
                if ((link_state == LINK_STATE_REPEAT_START) | (link_state == LINK_STATE_TRANSITION)) begin
                    scl_high_period      <= SCL_HIGH_PERIOD_I3C_LONG;
                    scl_high_period_div2 <= SCL_HIGH_PERIOD_I3C_LONG_DIV2;
                end
                else begin
                    scl_high_period      <= SCL_HIGH_PERIOD_I3C_SHORT;
                    scl_high_period_div2 <= SCL_HIGH_PERIOD_I3C_SHORT_DIV2;
                end
            end
            else begin // device == DEVICE_I2C
                if (mode == 3'h0) begin // 1MHz
                    scl_high_period      <= SCL_HIGH_PERIOD_I2C_1MHZ;
                    scl_high_period_div2 <= SCL_HIGH_PERIOD_I2C_1MHZ_DIV2;
                end
                else if (mode == 3'h1) begin // 400KHz
                    scl_high_period      <= SCL_HIGH_PERIOD_I2C_400KHZ;
                    scl_high_period_div2 <= SCL_HIGH_PERIOD_I2C_400KHZ_DIV2;
                end
                else if (mode == 3'h2) begin // 100kHz
                    scl_high_period      <= SCL_HIGH_PERIOD_I2C_100KHZ;
                    scl_high_period_div2 <=SCL_HIGH_PERIOD_I2C_100KHZ_DIV2;
                end
                else if (mode == 3'h3) begin // 100kHz
                    scl_high_period      <= SCL_HIGH_PERIOD_I2C_100KHZ;
                    scl_high_period_div2 <=SCL_HIGH_PERIOD_I2C_100KHZ_DIV2;
                end
                else if (mode == 3'h4) begin // 100kHz
                    scl_high_period      <= SCL_HIGH_PERIOD_I2C_100KHZ;
                    scl_high_period_div2 <=SCL_HIGH_PERIOD_I2C_100KHZ_DIV2;
                end
                else begin
                    // Error
                end
            end
        end
    end

    always_ff @(posedge clk or negedge reset_n) begin
        if (~reset_n) begin
            scl_low_period_cntr <= '0;
            scl_high_period_cntr <= '0;
            scl_status <= 1'b0;
        end
        else begin
            if ((request != REQUEST_IDLE) | busy) begin
                if (scl_low_period_cntr == scl_low_period) begin
                    scl_status <= 1'b1;

                    if (scl_high_period_cntr == scl_high_period_div2) begin
                        scl_status_middle <= 1'b1;
                    end

                    if (scl_high_period_cntr == scl_high_period) begin
                        scl_low_period_cntr <= '0;
                        scl_high_period_cntr <= '0;
                    end
                    else begin
                        scl_high_period_cntr <= scl_high_period_cntr + 9'h001;
                    end
                end
                else begin
                    scl_status <= 1'b0;
                    scl_status_middle <= 1'b0;
                    scl_low_period_cntr <= scl_low_period_cntr + 9'h001;
                end
            end
        end
    end

    // External pull-up resistor
    always_ff @(posedge clk or negedge reset_n) begin
        if (~reset_n) begin
            pur_oe <= 1'b1;
        end
        else begin
            if (device == DEVICE_I3C) begin
                if (link_mode == PUSH_PULL) begin
                    pur_oe <= 1'b0;
                end
                else begin
                    pur_oe <= 1'b1;
                end
            end
            else begin // device == DEVICE_I2C
                pur_oe <= 1'b1;
            end
        end
    end

    // SCL status rising edge
    always_ff @(posedge clk) scl_status_ff <= scl_status;
    assign scl_status_r_edge = scl_status & ~scl_status_ff;

     // Stage last - SCL status falling edge
    assign stage_last = ~scl_status & scl_status_ff;

    // IBI detectection
    logic [1:0] ibi_detected_filter;
    logic ibi_detected_single;
    logic ibi_detected_temp;
    logic ibi_detected_temp_ff;
    assign ibi_detected_temp = &ibi_detected_filter;

    always_ff @(posedge clk) ibi_detected_temp_ff <= ibi_detected_temp;
    assign ibi_detected = ibi_detected_temp & ~ibi_detected_temp_ff;

    always_ff @(posedge clk or negedge reset_n) begin
        if (~reset_n) begin
            ibi_detected_filter <= '0;
        end
        else begin
            if (ibi_detected_single) begin
                ibi_detected_filter[0] <= 1'b1;
                ibi_detected_filter[1] <= ibi_detected_filter[0];
            end
            else begin
                ibi_detected_filter <= '0;
            end
        end
    end

    // Generate Link State
    always_ff @(posedge clk or negedge reset_n) begin
        if (~reset_n) begin
            scl_data_out                <= 1'b1;
            sda_data_out                <= 1'b1;
            sda_oe                      <= 1'b0;
            ibi_detected_single         <= 1'b0;
            ack_nack_status             <= 1'b0;
            ack_nack_status_valid       <= 1'b0;
            transition_captured         <= TRANSITION_END;
            transition_status           <= TRANSITION_STATUS_CNT;
            transition_status_valid     <= 1'b0;
            cap_data_valid              <= 1'b0;
        end
        else begin
            if (operation_mode == MASTER_ONLY) begin
                if (link_state == LINK_STATE_IDLE) begin 
                    // Check IBI
                    if (~sda_data_in) begin
                        ibi_detected_single <= 1'b1;
                    end
                    else begin
                        ibi_detected_single <= 1'b0;
                    end

                    sda_oe <= 1'b0;
                end
                else if (link_state == LINK_STATE_START) begin
                    if (scl_status | stage_last) begin
                        sda_oe       <= 1'b1;
                        sda_data_out <= 1'b0;
                    end
                    else begin
                        sda_oe       <= 1'b0;
                    end

                    scl_data_out <= ~stage_last;
                end
                else if (link_state == LINK_STATE_REPEAT_START) begin
                    sda_oe       <= 1'b1;
                    sda_data_out <= ~(scl_status_middle | stage_last);

                    scl_data_out <= scl_status;
                end
                else if (link_state == LINK_STATE_STOP) begin
                    sda_oe       <= 1'b1;

                    if (stage_last) begin
                        sda_data_out <= 1'b1;
                    end
                    else begin
                        sda_data_out <= 1'b0;
                    end

                    scl_data_out <= scl_status | stage_last;
                end
                else if (link_state == LINK_STATE_WRITE) begin
                    if (link_mode == PUSH_PULL) begin
                        sda_data_out <= gen_data;
                        sda_oe       <= 1'b1;
                    end
                    else begin
                        sda_data_out <= 1'b0;
                        sda_oe       <= ~gen_data;
                    end

                    scl_data_out <= scl_status;
                end
                else if (link_state == LINK_STATE_READ) begin
                    sda_oe         <= 1'b0;
                    cap_data_valid <= scl_status_r_edge;
                    scl_data_out   <= scl_status;
                end
                else if (link_state == LINK_STATE_ACK_NACK) begin
                    if (scl_status | stage_last) begin
                        sda_oe <= 1'b1;

                        

                        if (scl_status_r_edge) begin
                            sda_data_out          <= sda_data_in;
                            ack_nack_status       <= sda_data_in;
                            ack_nack_status_valid <= 1'b1;
                        end
                        else begin
                            sda_data_out          <= ack_nack_status;
                            ack_nack_status_valid <= 1'b0;
                        end
                    end
                    else begin
                        sda_oe                <= 1'b0;
                        ack_nack_status_valid <= 1'b0;
                    end

                    scl_data_out <= scl_status;
                end
                else if (link_state == LINK_STATE_TRANSITION) begin
                    sda_oe <= scl_status | stage_last;

                    if (scl_status_r_edge) begin
                        transition_captured <= transition_t'(sda_data_in);
                        sda_data_out        <= sda_data_in;

                        if (transition_t'(sda_data_in) == TRANSITION_CNT) begin
                            if (transition_request == TRANSITION_END) begin
                                transition_status <= TRANSITION_STATUS_MASTER_END;
                            end
                            else begin
                                transition_status <= TRANSITION_STATUS_CNT;
                            end
                        end
                        else begin
                            if (transition_request == TRANSITION_END) begin
                                transition_status <= TRANSITION_STATUS_SLAVE_MASTER_END;
                            end
                            else begin
                                transition_status <= TRANSITION_STATUS_SLAVE_END;
                            end
                        end

                    end
                    else if (scl_status_middle | stage_last) begin
                        sda_data_out <= ~|transition_status;
                    end
                    else begin
                        if (scl_status) begin
                            sda_data_out <= transition_captured;
                        end
                    end

                    transition_status_valid <= scl_status_r_edge;

                    scl_data_out <= scl_status;
                end
                else begin
                    scl_data_out            <= 1'b0;
                    sda_oe                  <= 1'b0;
                    sda_data_out            <= 1'b0;
                end
            end
            else begin
                scl_data_out <= 1'b1;
                sda_oe       <= 1'b0;
                sda_data_out <= 1'b1;
            end
        end
    end

    // SCL direction
    logic scl_direction;

    always_ff @(posedge clk or negedge reset_n) begin
        if (~reset_n) begin
            scl_oe <= 1'b0;
        end
        else begin
            if (operation_mode == MASTER_ONLY) begin
                scl_oe <= 1'b1;
            end
            else if (operation_mode == SECONDARY_MASTER) begin
                scl_oe <= scl_direction;
            end
            else if (operation_mode == SLAVE_ONLY) begin
                scl_oe <= 1'b0;
            end
        end
    end

endmodule
